# SPDX-License-Identifier: MIT
"""Runtime helper invoked by generated Makefiles."""

from __future__ import annotations

import argparse
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Sequence

from .generator import GENERATED_HEADER


def main(argv: Sequence[str] | None = None) -> int:
    parser = argparse.ArgumentParser(prog="python -m pmake.runtime")
    subparsers = parser.add_subparsers(dest="command", required=True)
    p_run = subparsers.add_parser("run", help="Execute a target using metadata")
    p_run.add_argument("--meta", type=Path, required=True, help="Path to target metadata JSON")
    p_run.add_argument(
        "--status",
        choices=["success", "fail", "skip"],
        help="Override execution status (default success unless PMAKE_STATUS env set)",
    )

    args = parser.parse_args(argv)
    if args.command != "run":
        parser.error(f"Unknown command {args.command!r}")

    status = args.status or os.environ.get("PMAKE_STATUS", "success")
    meta = _load_meta(args.meta)

    if status == "skip":
        print(f"pmake-runner: skipping {meta['name']} (status=skip)")
        return 0

    worklog = Path(meta["paths"]["worklog"])
    error_log = Path(meta["paths"]["error"])
    err_include = Path(meta["paths"]["err_include"])
    state_ok = Path(meta["paths"]["state_ok"])
    state_fail = Path(meta["paths"]["state_fail"])

    worklog.parent.mkdir(parents=True, exist_ok=True)
    error_log.parent.mkdir(parents=True, exist_ok=True)
    err_include.parent.mkdir(parents=True, exist_ok=True)
    state_ok.parent.mkdir(parents=True, exist_ok=True)

    instruction = _format_instruction(meta)
    timestamp = datetime.now(timezone.utc).isoformat()

    if status == "success":
        _append_worklog(worklog, timestamp, meta, instruction)
        _archive_error(error_log)
        _remove_include(err_include)
        _write_stamp(state_ok, timestamp)
        _remove_stamp(state_fail)
        print(f"pmake-runner: {meta['name']} completed (logged at {worklog})")
        return 0

    # failure path
    _append_error(error_log, timestamp, meta, instruction)
    attempts = _count_attempts(error_log)
    _write_include(err_include, error_log, attempts, timestamp)
    _write_stamp(state_fail, timestamp)
    _remove_stamp(state_ok)
    print(f"pmake-runner: {meta['name']} FAILED (details in {error_log})", file=sys.stderr)
    return 1


def _load_meta(path: Path) -> Dict[str, object]:
    if not path.exists():
        raise SystemExit(f"pmake-runner: metadata not found at {path}")
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise SystemExit(f"pmake-runner: invalid metadata at {path}: {exc}") from exc


def _format_instruction(meta: Dict[str, object]) -> str:
    deps = meta.get("deps", {})
    globals_list = meta.get("globals", [])
    lines = [
        f"Implement target '{meta['name']}' ({meta.get('kind')}).",
        f"Executor: {meta.get('executor')}.",
    ]
    if globals_list:
        lines.append("Include globals via -inc:")
        for item in globals_list:
            lines.append(f"  - {item}")
    skills = deps.get("skills", []) if isinstance(deps, dict) else []
    if skills:
        lines.append("Skill references (-lib):")
        for item in skills:
            lines.append(f"  - {item}")
    externs = deps.get("extern", []) if isinstance(deps, dict) else []
    if externs:
        lines.append("External dependencies:")
        for item in externs:
            lines.append(f"  - {item}")
    lines.append(f"Write worklog to: {meta['paths']['worklog']}")
    lines.append(f"On failure, write err log to: {meta['paths']['error']}")
    return "\n".join(lines)


def _append_worklog(path: Path, timestamp: str, meta: Dict[str, object], instruction: str) -> None:
    header = f"## Attempt at {timestamp}\n"
    content = (
        f"{header}"
        f"Status: success\n"
        f"Target: {meta['name']}\n"
        f"Executor: {meta.get('executor')}\n"
        f"{instruction}\n\n"
    )
    with path.open("a", encoding="utf-8") as fh:
        fh.write(content)


def _append_error(path: Path, timestamp: str, meta: Dict[str, object], instruction: str) -> None:
    header = f"## Attempt at {timestamp}\n"
    content = (
        f"{header}"
        f"Status: failure\n"
        f"Target: {meta['name']}\n"
        f"Executor: {meta.get('executor')}\n"
        f"{instruction}\n\n"
    )
    with path.open("a", encoding="utf-8") as fh:
        fh.write(content)


def _archive_error(path: Path) -> None:
    if not path.exists():
        return
    archive_dir = path.parent / "archive"
    archive_dir.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    archive_path = archive_dir / f"{path.name}.{timestamp}"
    path.replace(archive_path)


def _write_include(path: Path, error_log: Path, attempts: int, timestamp: str) -> None:
    content = (
        GENERATED_HEADER
        + f"PMAKE_ERR_LOG_{_normalize_var(path.stem)} := {error_log}\n"
        + f"PMAKE_ATTEMPTS_{_normalize_var(path.stem)} := {attempts}\n"
        + f"PMAKE_LAST_ATTEMPT_{_normalize_var(path.stem)} := {timestamp}\n"
    )
    path.write_text(content, encoding="utf-8")


def _remove_include(path: Path) -> None:
    if path.exists():
        path.unlink()


def _write_stamp(path: Path, timestamp: str) -> None:
    path.write_text(timestamp + "\n", encoding="utf-8")


def _remove_stamp(path: Path) -> None:
    if path.exists():
        path.unlink()


def _count_attempts(path: Path) -> int:
    if not path.exists():
        return 1
    text = path.read_text(encoding="utf-8")
    count = text.count("## Attempt")
    return count if count else 1


def _normalize_var(name: str) -> str:
    return name.replace("-", "_").upper()


if __name__ == "__main__":
    sys.exit(main())
